﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Proggy.Web.Cms.ContentModel;
using Proggy.Data;
using System.Reflection;
using Proggy.Web.Cms.PropertyTypes;

namespace Proggy.Web.Cms.Repositories.ContentModel
{
    /// <summary>
    /// Content model repo
    /// </summary>
    public class ContentModelTypeRepository : IContentModelTypeRepository
    {
        public ContentModelTypeRepository()
        {

            // start
            var output = new List<AvailableContentModel>();


            // get assemblies
            var assemblies = AppDomain.CurrentDomain.GetAssemblies().Where(x => !x.GlobalAssemblyCache).ToList();

            // iterate
            foreach (var assembly in assemblies)
            {

                // get types
                var types =
                    assembly
                    .GetTypes()
                    .Where(x =>
                        x.IsSubclassOf(typeof(ContentModelBase))
                        &&
                        x.GetInterfaces().ToList().Contains(typeof(IContentModel))
                        &&
                        !x.IsAbstract)
                    .ToList();


                //
                //types.ForEach(t => output.Add(new AvailableContentModel(t)));

                // iterate and add
                foreach (var type in types)
                {

                    // create new content model type
                    var cmt = new AvailableContentModel();

                    // set
                    // set core propeties
                    cmt.TypeAlias = type.Name.Substring(0, type.Name.Length - ProggyDataConstants.CONTENT_MODEL_APPENDAGE.Length);
                    cmt.AssemblyName = type.Assembly.FullName;
                    cmt.Namespace = type.Namespace;
                    cmt.Type = type;
                    cmt.ContentModelAttribute = this.getViewModelAttributeFromType(type);
                    cmt.CanBeUsedForRootNode = cmt.ContentModelAttribute.CanBeRootNode;
                    cmt.FriendlyName = cmt.ContentModelAttribute.FriendlyName;
                    cmt.ControllerNamespace = cmt.ContentModelAttribute.ControllerNamespace;

                    // start type dictionary
                    var typePropertyDic = new Dictionary<string, List<Tuple<ContentModelPropertyTypeAttribute, PropertyInfo>>>();

                    // now set tabs
                    foreach (var _prop in type.GetProperties())
                    {
                        // get attributes
                        var attr = _prop.GetCustomAttributes(typeof(ContentModelPropertyTypeAttribute), true).ToList();

                        // check count
                        if (attr.Count > 1)
                        {
                            throw new Exception("Tab attribute can only be used once per property");
                        }

                        // check count
                        if (attr.Count == 1)
                        {
                            // get the tab attribute
                            var _attr = (ContentModelPropertyTypeAttribute)attr.First();

                            // get tab name
                            var name = _attr.TabName;

                            // check dic
                            if (typePropertyDic.ContainsKey(name))
                            {
                                // add to
                                typePropertyDic[name].Add(Tuple.Create(_attr, _prop));
                            }
                            else
                            {
                                // create new
                                typePropertyDic.Add(
                                    name,
                                    new List<Tuple<ContentModelPropertyTypeAttribute, PropertyInfo>> { Tuple.Create(_attr, _prop) });
                            }
                        }
                    }

                    // got dictionary, so now create tabs
                    cmt.Tabs = typePropertyDic.Select(t => new AvailableTab
                    {
                        Name = t.Key,
                        PropertyTypes = this.getAvailableTypesFromPropertyInfo(t.Value)
                    });

                    // set allowed children
                    cmt.AllowedChildTypeAliases = cmt.ContentModelAttribute.AllowedChildViewModelTypeAliases == null ? null : cmt.ContentModelAttribute.AllowedChildViewModelTypeAliases.ToList();

                    // set allowed templates
                    cmt.AllowedTemplates = cmt.ContentModelAttribute.AllowedTemplates.ToList();

                    // add
                    output.Add(cmt);

                }
            }



            /**** BASE CLASS REMOVAL ****/

            // start type matches
            var typeMatches = new List<Type>();

            // now check output for inheritance issues
            foreach (var acmt in output)
            {
                //
                if (acmt.ContentModelAttribute.IgnoreBaseClass)
                {
                    // get base class
                    var baseT = acmt.Type.BaseType;

                    // add to matches
                    typeMatches.Add(baseT);
                }
            }

            // iterate matches and remove
            output.Where(x => typeMatches.Contains(x.Type)).ToList().ForEach(t => output.Remove(t));

            // check where all children should be available
            foreach (var cmt in output.Where(x => x.AllowedChildTypeAliases != null && x.AllowedChildTypeAliases.Count == 0))
            {
                cmt.AllowedChildTypeAliases = output.Select(x => x.TypeAlias).ToList();
            }

            // set
            this._availableModelTypesList = output;

        }

        /// <summary>
        /// Available content model types
        /// </summary>
        public IList<AvailableContentModel> AvailableContentModels
        {
            get
            {
                return this._availableModelTypesList;
            }
        }

        /// <summary>
        /// Available child types from type
        /// </summary>
        /// <param name="availableType"></param>
        /// <returns></returns>
        public IList<AvailableContentModel> GetAvailableChildTypesFromContentModel(AvailableContentModel availableType)
        {
            if (availableType.AllowedChildTypeAliases == null) {
                return new List<AvailableContentModel>(0);
            }
            return this.AvailableContentModels.Where(x => availableType.AllowedChildTypeAliases.Contains(x.TypeAlias)).ToList();
        }

        ///private
        private List<AvailableContentModel> _availableModelTypesList;

        /// <summary>
        /// Create property types from property info tabs
        /// </summary>
        /// <param name="propertyTypeList"></param>
        /// <returns></returns>
        private IEnumerable<AvailablePropertyType> getAvailableTypesFromPropertyInfo(List<Tuple<ContentModelPropertyTypeAttribute, PropertyInfo>> propertyTypeList)
        {
            // create output
            var output = new List<AvailablePropertyType>();

            // iterate
            foreach (var item in propertyTypeList)
            {
                // set prop attribute
                // split
                var propInfo = item.Item2;
                var propAttr = item.Item1;

                // get attributes
                var propTypeAttr = propInfo.PropertyType.GetCustomAttributes(typeof(PropertyTypeAttribute), true).ToList();

                // check attr
                if (propTypeAttr.Count < 1 || propTypeAttr.Count > 1)
                {
                    throw new Exception("PropertyTypeAttribute can only be used once per class.");
                }

                // create new and add basic props
                var availPropType = new AvailablePropertyType
                {
                    FriendlyName = item.Item1.FriendlyName,
                    PropertyName = item.Item2.Name,
                    PropertyType = item.Item2.PropertyType
                };

                // set
                availPropType.PropertyTypeAttribute = (PropertyTypeAttribute)propTypeAttr.First();
                availPropType.RequiresConfiguration = item.Item2.PropertyType.IsSubclassOf(typeof(ConfigurablePropertyTypeBase)) && availPropType.PropertyTypeAttribute.RequiredConfigObjectType != null;

                // add to list
                output.Add(availPropType);
            }

            //
            return output;
        }

        /// <summary>
        /// Returns the front end view model attribute from the type
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private ContentModelAttribute getViewModelAttributeFromType(Type type)
        {
            // get attr
            var customAttributes = type.GetCustomAttributes(typeof(ContentModelAttribute), false).ToList();

            // check
            if ((customAttributes.Count < 1 || customAttributes.Count > 1))
            {
                throw new Exception("Non-abstract ContentModel types must have a single ContentModelAttribute decoration");
            }

            // set attr
            return (ContentModelAttribute)customAttributes.First();
        }
    }
}
